package org.deltaset.samples.mvc.base;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.OrderColumn;
import org.deltaset.meta.EntityResolver;
import org.deltaset.meta.MetaField;

/**
 *
 * @author dmakariev
 */
class EntityResolverImpl implements EntityResolver, Serializable {

    @Override
    public boolean isEntity(Class checkClass) {
        return null != checkClass.getAnnotation(Entity.class);
    }

    @Override
    public Object extractEntityId(Object entity) {
        return extractEntityIdInternal(entity);
    }

    @Override
    public Object convertToEntityId(String id, Class entityClass) {
        return convertToEntityIdInternal(id, entityClass);
    }

    @Override
    public String uniqueStringId(Object entity) {
        if (null == entity) {
            return null;
        }
        final Class entityClass = entity.getClass();
        final String entityId = String.valueOf(this.extractEntityId(entity));
        return entityClass.getName() + ":" + entityId;
    }

    @Override
    public boolean isRemovableCollection(MetaField metaField) {
        final Annotation[] annotations = metaField.getOriginalAnnotations();
        boolean result = false;
        for (Annotation annotation : annotations) {
            if (annotation.annotationType().equals(OneToMany.class)) {
                final boolean isRemovable = ((OneToMany) annotation).orphanRemoval();
                result = isRemovable;
                //return true;
            }
            if (annotation.annotationType().equals(ManyToMany.class)) {
                return true;
            }
            if (annotation.annotationType().equals(JoinTable.class)) {
                return true;
            }
        }
        return result;
    }

    @Override
    public boolean isOrderableCollection(MetaField metaField) {
        final Annotation[] annotations = metaField.getOriginalAnnotations();
        for (Annotation annotation : annotations) {
            if (annotation.annotationType().equals(OrderColumn.class)) {
                return true;
            }
        }
        return false;
    }

    static Object extractEntityIdInternal(Object entity) {
        try {
            final Field field = findIdField(entity.getClass());
            return field.get(entity);
        } catch (NullPointerException npe) {
            return null;
        } catch (IllegalArgumentException ex) {
            throw new RuntimeException("Cannot extract id from class=" + entity.getClass(), ex);
        } catch (IllegalAccessException ex) {
            throw new RuntimeException("Cannot extract id from class=" + entity.getClass(), ex);
        }
    }

    private static Object convertToEntityIdInternal(String id, Class entityClass) {
        final Field field = findIdField(entityClass);
        final Class fieldClass = field.getType();

        if (String.class.isAssignableFrom(fieldClass)) {
            return id;
        } else if (Long.class.isAssignableFrom(fieldClass)) {
            return Long.valueOf(id);
        } else if (Integer.class.isAssignableFrom(fieldClass)) {
            return Integer.valueOf(id);
        }
        throw new UnsupportedOperationException("ERROR Not Supported Id class=" + fieldClass);
    }

    private static Field findIdField(final Class clazz) {
        final List<Field> found = findFieldByIdAnnotation(clazz);
        if (1 != found.size()) {
            throw new RuntimeException("ERROR expected SINGLE @Id field but found " + found.size());
        }
        return found.get(0);
    }

    private static List<Field> findFieldByIdAnnotation(final Class clazz) {
        final List<Field> foundFields = new ArrayList<Field>();
        filterFields(foundFields, clazz, new IdFilter());

        if (foundFields.isEmpty()) {
            throw new RuntimeException("Cannot find field for class=" + clazz + " with annotation=" + Id.class);
        }
        return Collections.unmodifiableList(foundFields);
    }

    private static class IdFilter implements Filter<Field> {

        @Override
        public boolean isSatisfied(Field field) {
            final Annotation annotation = field.getAnnotation(Id.class);
            if (null != annotation) {
                field.setAccessible(true);
                return true;
            }
            return false;
        }
    }

    private static void filterFields(final List<Field> foundFields, final Class clazz, final Filter<Field> filter) {
        if (null == clazz) {
            return;
        }
        final Field[] fields = clazz.getDeclaredFields();
        for (int i = 0; i < fields.length; i++) {
            final Field field = fields[i];
            if (true == filter.isSatisfied(field)) {
                foundFields.add(field);
            }
        }
        filterFields(foundFields, clazz.getSuperclass(), filter);
    }

    private interface Filter<T> {

        boolean isSatisfied(final T object);
    }
}
